import java.util.Timer;
import java.util.TimerTask;

public class Router {

	// modelo do roteador, e sua entrada e saida.
	private Modelo modelo;
	private IO io;
	// ultimo timestamp de envio do vetor, para evitar timeout.
	private long last_sent;
	
	private RouterWindow window;

	/**
	 * Cria um roteador a partir de seu identificador e tempo de timeout.
	 * 
	 * @param id
	 *            identificador do roteador.
	 * @param time
	 *            tempo de timeout.
	 */
	public Router(int id, int time) {
		modelo = new Modelo(id, time);
		io = new IO();
		last_sent = 0;
	}

	/**
	 * Inicia o modelo a partir dos arquivos de configuracao. Inicia o socket do
	 * io, a partir do modelo.
	 * 
	 * @throws Exception
	 *             na leitura dos arquivos ou na criacao do socket.
	 */
	public void initParts() throws Exception {
		IO.lerArquivos(modelo);
		io.createSocket(modelo.getPort());
		window = new RouterWindow(this);
		window.showWindow();
	}

	/**
	 * Executa o algoritmo de vetor distancia.
	 * 
	 * @throws Exception
	 *             pode ocorrer durante o envio de mensagens aos vizinhos.
	 */
	public void bellmanFord() throws Exception {
		for (Integer y : modelo.getRoteadores())
			modelo.setDistance(y, modelo.getCost(y), y);

		// o segundo passo do algoritmo eh desnecessario, visto que Dw(y) e
		// infinito por padrao

		sendMessageToAllLinks();

		while (true)
			if (modelo.processVector(io.getMessage())) {
				if (calculateDistance())
					sendMessageToAllLinks();
			}
	}

	/**
	 * Calcula as distancias, atualizando o modelo.
	 * 
	 * @return true se alguma distancia se modificou.
	 */
	private synchronized boolean calculateDistance() {
		boolean modified = false;

		for (Integer y : modelo.getRoteadores()) {
			int min = Modelo.INFINITO;
			int next_hop = Modelo.INFINITO;

			for (Integer w : modelo.getTodosEnlaces()) {
				int trial = modelo.getCost(w) + modelo.getDistance(w, y);

				if (trial < min) {
					min = trial;
					next_hop = w;
				}
			}

//			if (modelo.getTodosEnlaces().contains(y) && min == Modelo.INFINITO) {
//				System.out.println(y + " deve estar morto.");
//				System.out.println("Custo -> " + modelo.getCost(y));
//				System.out.println("GetDistance(y, y) == 0? -> " + modelo.getDistance(y, y));
//				System.out.println("Faz " + (modelo.timeStamp() - modelo.getTimestamps().get(y)) + " milisegundos.");
//			}
			
			if (min != modelo.getDistance(modelo.getId(), y))
				modified = true; // modificou o valor de alguma
			// distancia

			modelo.setDistance(y, min, next_hop);
		}
		return modified;
	}

	/**
	 * Envia os vetores aos vizinhos.
	 * 
	 * @throws Exception
	 *             lancada caso falhe o envio atraves do socket.
	 */
	private synchronized void sendMessageToAllLinks() throws Exception {
		for (int w : modelo.getTodosEnlaces()){
			String result = modelo.createMessage(w);
			io.sendMessage(modelo.getEnlace(w), result);
			window.writeMessage("Sent to: " + modelo.getId() + "-> <" + result + ">");
		}
		last_sent = modelo.timeStamp();
	}

	/**
	 * Inicia a segunda thread, que dorme durante time envia um vetor distancia
	 * a cada time.
	 */
	private void initSecondThread() {
		Timer t = new Timer(true);

		t.schedule(new TimerTask() {
//			int i = 0;
			@Override
			public void run() {
				
				window.clearTable();
//				System.out.println("Tabela de Roteamento " + (i++) + " (Roteador "+ modelo.getId() + "):");
//				System.out.println("id\t|\tdistancia\t|\tsalto");
				for (int y : modelo.getRoteadores()) {
//					System.out.println(y + "\t|\t" + modelo.getDistance(modelo.getId(), y) + "\t\t|\t" + modelo.getNextHop(y));
					window.addRow(y);
				}
//				System.out.println();
				if (modelo.timeStamp() - last_sent >= modelo.getTime())
					try {
						calculateDistance();
						sendMessageToAllLinks();
					} catch (Exception e) {
						e.printStackTrace();
					}
			}

		}, modelo.getTime(), modelo.getTime());
	}

	public Modelo getModel(){
		return this.modelo;
	}
	
	public void formatTabela() {
		
	}
	
	public static void main(String[] args) throws Exception {
		if (args.length != 2) {
			System.out.println("USAGE: java Router <id> <time>");
			System.exit(1);
		}

		int id = Integer.parseInt(args[0]);
		int time = Integer.parseInt(args[1]);

		Router r = new Router(id, time);
		r.initParts();
		r.initSecondThread();
		r.bellmanFord();
	}

}
